package cn.hacynn.nd.service.impl;

import cn.hacynn.nd.common.constant.MessageConstant;
import cn.hacynn.nd.common.context.BaseContext;
import cn.hacynn.nd.common.exception.PasswordErrorException;
import cn.hacynn.nd.common.exception.UserException;
import cn.hacynn.nd.common.exception.UserLoginException;
import cn.hacynn.nd.common.properties.OssProperties;
import cn.hacynn.nd.common.properties.STSProperties;
import cn.hacynn.nd.common.result.Result;
import cn.hacynn.nd.common.utils.OSSUtil;
import cn.hacynn.nd.common.utils.JavaMailUtil;
import cn.hacynn.nd.common.utils.RedisUtils;
import cn.hacynn.nd.mapper.UserMapper;
import cn.hacynn.nd.pojo.dto.*;
import cn.hacynn.nd.pojo.entity.User;
import cn.hacynn.nd.pojo.vo.STSVO;
import cn.hacynn.nd.service.UserService;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;


/**
 * @Author Hacynn
 * @Date 2024/5/24 22:56
 * @Version 1.0
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Value("${nd.user.initSpace}")
    private Long initSpace;
    @Value("${nd.redis.emailCodeLifetime}")
    private Long emailCodeLifetime;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OSSUtil ossUtil;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private STSProperties stsProperties;
    @Autowired
    private OssProperties ossProperties;
    @Autowired
    private ExecutorService executor;
    @Autowired
    private JavaMailUtil javaMailUtil;

    /**
     * 用户登录
     * @param userLoginDTO
     * @return
     */
    @Override
    public User login(UserLoginDTO userLoginDTO) {
        String account = userLoginDTO.getAccount();
        String password = userLoginDTO.getPassword();

        User user = userMapper.getByAccount(account);
        if (user == null) {
            // 账号不存在
            throw new UserLoginException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        if (user.getDeleted()) {
            // 账号被删除
            throw new UserLoginException(MessageConstant.ACCOUNT_DELETED);
        }
        // 使用BCrypt算法校验密码
        boolean checkPW = BCrypt.checkpw(password, user.getPassword());
        if (!checkPW) {
            // 密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        // 将数据清空和格式化后再传回前端
        user.setPassword(null);
        user.setSafeCode(null);
        user.setBucketName(null);
        if (user.getLastLoginTime() != null) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
            user.setLastLoginTimeStr(simpleDateFormat.format(user.getLastLoginTime()));
            user.setCreateTimeStr(simpleDateFormat.format(user.getCreateTime()));
        }
        return user;
    }

    /**
     * 获取当前用户信息
     * @return
     */
    @Override
    public User currentUser() {
        Long currentId = BaseContext.getCurrentId();
        if (currentId == null) {
            // 未登录
            throw new UserLoginException(MessageConstant.USER_NOT_LOGIN);
        }
        User user = userMapper.selectById(currentId);

        // 将数据清空和格式化后再传回前端
        user.setPassword(null);
        user.setSafeCode(null);
        user.setBucketName(null);
        if (user.getLastLoginTime() != null) {
            //使用 SimpleDateFormat 类创建了一个格式化日期时间的对象 simpleDateFormat
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            //simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"))设置了时区为 GMT+8
            simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
            //simpleDateFormat.format(user.getLastLoginTime())将 user 对象的 lastLoginTime 属性（假设是一个 Date 类型的对象）格式化为指定格式的字符串。
            user.setLastLoginTimeStr(simpleDateFormat.format(user.getLastLoginTime()));
            //simpleDateFormat.format(user.getCreateTime())将 user 对象的 createTime 属性（假设也是一个 Date 类型的对象）格式化为指定格式的字符串。
            user.setCreateTimeStr(simpleDateFormat.format(user.getCreateTime()));
        }
        return user;
    }

    /**
     * 获取当前用户bucketName
     * @return
     */
    @Override
    public String getBucketName() {
        //获取当前用户信息
        Long currentId = BaseContext.getCurrentId();
        User user = userMapper.selectById(currentId);
        if (user == null) {
            // 用户未登录
            throw new UserLoginException(MessageConstant.USER_NOT_LOGIN);
        }
        return user.getBucketName();
    }

    /**
     * 上传头像
     * @param file
     * @return
     */
    @Override
    public String uploadAvatar(MultipartFile file) {
        if (file == null) {
            // 头像信息为空
            throw new UserException(MessageConstant.UPLOAD_USER_AVATAR_IS_NULL);
        }
        //获取用户信息
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.selectById(userId);
        // 上传头像到oss
        String url = null;
        try {
            //上传图像
             url = ossUtil.uploadAvatar(file, userId);
        } catch (IOException e) {
            // 上传头像失败
            throw new UserException(MessageConstant.USER_AVATAR_UPLOAD_FAILED);
        }
        // 将路径保存到数据库
        user.setAvator(url);
        user.setUpdateTime(new Date());
        userMapper.updateById(user);
        return url;
    }

    /**
     * 修改昵称
     * @param name
     */
    @Override
    public void updateName(String name) {
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.selectById(userId);
        if (user == null) {
            // 用户不存在
            throw new UserException(MessageConstant.USER_NOT_EXIST);
        }
        user.setName(name);
        user.setUpdateTime(new Date());
        userMapper.updateById(user);
    }

    /**
     * 生成 STS 凭证
     * @return
     */
    @Override
    public STSVO getSTS() {
        //获取用户信息
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.selectById(userId);
        String stsVo = (String) redisUtils.get(stsProperties.getStsPrefix() + user.getBucketName());
        if (stsVo != null) {
            // redis 中有，无需新建授权，直接返回原授权凭证
            return JSONUtil.toBean(stsVo, STSVO.class);
        }
        // 生成 STS 授权凭证
        //生成sts对象
        STSVO sts = ossUtil.generateStsToken();
        //向sts存入信息为后续使用
        sts.setRegion(ossProperties.getRegion());
        sts.setBucketName(user.getBucketName());
        // 存入 redis 中，一天过期时间
        redisUtils.set(stsProperties.getStsPrefix() + user.getBucketName(), JSONUtil.toJsonStr(sts), 43200L);
        return sts;
    }

    /**
     * 发送验证码到邮箱
     * @param email
     */
    @Override
    public void sendCode(String email) {
        String code = String.valueOf((int) ((Math.random() * 9 + 1) * Math.pow(10, 6 - 1)));   // 6位验证码
        String subject = "【Datum】验证码";
        String content = "<html>" +
                "<head>" +
                "<style>" +
                "  body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }" +
                "  .container { max-width: 600px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }" +
                "  h1 { color: #333; }" +
                "  p { font-size: 16px; line-height: 1.6; color: #666; }" +
                "  .code { display: inline-block; padding: 10px 20px; font-size: 24px; color: #fff; background-color: #007BFF; border-radius: 5px; text-decoration: none; }" +
                "  .logo { text-align: center; margin-bottom: 20px; }" +
                "</style>" +
                "</head>" +
                "<body>" +
                "<div class='container'>" +
                "  <div class='logo'>" +
                "    <img src='https://network-disk-avatar-1.oss-cn-beijing.aliyuncs.com/logo/logo.png' alt='Website Logo' width='200'>" +  // Replace with your actual logo URL
                "  </div>" +
                "  <h1>【Datum】验证码</h1>" +
                "  <p>您好，感谢您使用我们的服务。</p>" +
                "  <p>您的验证码如下，请在验证页面输入此验证码以完成验证：</p>" +
                "  <p class='code'>" + code +"</p>" +
                "  <p>请注意：此验证码将在10分钟后失效。</p>" +
                "  <p>如果您没有请求此验证码，请忽略此邮件。</p>" +
                "  <p>祝您愉快！</p>" +
                "</div>" +
                "</body>" +
                "</html>";
        // 将发送邮箱的任务提交到线程池
        executor.submit(() -> {
            try {
                // 发送验证码到邮箱
                javaMailUtil.sendMailForOnePeople(email, subject, content);
                // MailUtil.send(email, subject, content, true);  // hutool整合的邮箱发送太慢了（主机的DNS，获取host时间太长）
                log.info("向‘{}’发送了验证码‘{}’", email, code);
                // redis写入验证码，设置10分钟有效期
                redisUtils.set("code:" + email, code, emailCodeLifetime);
            } catch (Exception e) {
                log.error("向'{}'发送验证码时出现了异常，发送失败！", email, e);
            }
        });
    }

    /**
     * 发送初始密码到邮箱
     * @param email
     * @param initialPassword
     */
    public void sendInitialPassword(String email, String initialPassword) {
        String subject = "【Datum】注册成功";
        String content = "<html>" +
                "<head>" +
                "<style>" +
                "  body { font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; }" +
                "  .container { max-width: 600px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }" +
                "  h1 { color: #333; }" +
                "  p { font-size: 16px; line-height: 1.6; color: #666; }" +
                "  .password { display: inline-block; padding: 10px 20px; font-size: 24px; color: #fff; background-color: #007BFF; border-radius: 5px; text-decoration: none; }" +
                "  .logo { text-align: center; margin-bottom: 20px; }" +
                "</style>" +
                "</head>" +
                "<body>" +
                "<div class='container'>" +
                "  <div class='logo'>" +
                "    <img src='https://network-disk-avatar-1.oss-cn-beijing.aliyuncs.com/logo/logo.png' alt='Website Logo' width='200'>" +  // Replace with your actual logo URL
                "  </div>" +
                "  <h1>【Datum】注册成功</h1>" +
                "  <p>恭喜您，成功在我们的平台完成了注册。</p>" +
                "  <p>您的初始密码如下，请使用此密码登录并及时更改密码：</p>" +
                "  <p class='password'>" + initialPassword +"</p>" +
                "  <p>请注意：请妥善保管此密码，不要泄露给他人。</p>" +
                "  <p>如果您没有进行此操作，请忽略此邮件。</p>" +
                "  <p>祝您愉快！</p>" +
                "</div>" +
                "</body>" +
                "</html>";

        // 将发送邮箱的任务提交到线程池
        //将发送邮件的任务提交到线程池中，以异步方式执行，避免阻塞主线程。
        executor.submit(() -> {
            try {
                // 发送初始密码到邮箱
                javaMailUtil.sendMailForOnePeople(email, subject, content);
                // MailUtil.send(email, subject, content, true);   // hutool整合的邮箱发送太慢了（主机的DNS，获取host时间太长）
                log.info("向‘{}’发送了初始密码‘{}’", email, initialPassword);
            } catch (Exception e) {
                log.error("向'{}'发送初始密码时出现了异常，发送失败！", email, e);
            }
        });
    }

    /**
     * 根据邮箱对用户进行注册
     * @param email
     * @return
     */
    private User register(String email) {
        String password = RandomUtil.randomString(6);  // 6位随机用户初始密码
        // 对密码进行加密操作     BCrypt哈希算法
        String encryptPassword = BCrypt.hashpw(password, BCrypt.gensalt());
        //builder 进行初始化对象
        User user = User.builder()
                .name(RandomUtil.randomString(8))   // 8位随机用户名称
                .account(email)
                .password(encryptPassword)
                .email(email)
                .totalSpace(initSpace)   // 默认空间大小为500MB
                .lastLoginTime(new Date())   // 最后一次登录
                .build();
        // 将用户记录插入数据库
        userMapper.insert(user);
        // 将回调的用户id用于创建bucket并获得bucketName
        String bucketName = ossUtil.createBucket(user.getId());
        log.info("Bucket创建成功:{}", bucketName);
        // 将bucketName更新到数据库
        user.setBucketName(bucketName);
        userMapper.updateById(user);
        log.info("用户注册成功！id:{}", user.getId());
        // 将用户的初始密码发送到邮箱中
        sendInitialPassword(email, password);
        return user;
    }

    /**
     * 使用邮箱进行登录，没有该用户则直接注册一个
     * 使用事务回滚的原因
     * @param emailLoginDTO
     */


    @Transactional  // 事务管理  数据一致性
    @Override
    public User emailLogin(EmailLoginDTO emailLoginDTO) {
        String email = emailLoginDTO.getEmail();
        String code = emailLoginDTO.getCode();
        if (email == null || code == null) {
            // 电子邮箱或者验证码不能为空
            throw new UserException(MessageConstant.EMAIL_OR_CODE_IS_NULL);
        }
        // 校验验证码
        String key = "code:" + email;

        if (!redisUtils.hasKey(key) || !redisUtils.get(key).equals(code) ) {  // 如果redis中没有或者用户输入的code与redis中不一致
            // 校验失败，验证码错误
            throw new UserException(MessageConstant.EMAIL_CODE_MISTAKE);
        }
        // 校验通过，清除redis中的验证码数据
        redisUtils.delete(key);
        // 根据邮箱查询用户
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
        // 判断是否已经重复创建
        if (user == null) {
            // 用户不存在，创建用户
            user = register(email);
        }
        return user;
    }

    /**
     * 更新用户密码
     * @param updatePasswordDTO
     */
    @Override
    public void updatePassword(UpdatePasswordDTO updatePasswordDTO) {
        String oldPassword = updatePasswordDTO.getOldPassword();
        String newPassword = updatePasswordDTO.getNewPassword();
        //判断前端传过来的账号和密码是否为空
        if (oldPassword.isEmpty() || newPassword.isEmpty()) {
            // 密码不能为空
            throw new PasswordErrorException(MessageConstant.PASSWORD_IS_NULL);
        }
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.selectById(userId);
        //BCrypt.checkpw() 是 BCrypt 库中的一个静态方法，用于验证给定的明文密码 oldPassword 是否与哈希密码 user.getPassword() 匹配。
        boolean checkPW = BCrypt.checkpw(oldPassword, user.getPassword());
        if (!checkPW) {
            // 密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        String hashPW = BCrypt.hashpw(newPassword, BCrypt.gensalt());
        user.setPassword(hashPW);
        user.setUpdateTime(new Date());
        int i = userMapper.updateById(user);
        //在多线程或者分布式系统中，如果同时有多个线程或者节点修改同一个用户的密码，可能会出现竞争条件。
        // 例如，一个线程可能已经成功更新了用户密码，而另一个线程在尝试更新时由于版本号冲突或者其他原因导致更新失败
        // （影响行数为0）。
        if (i == 0) {
            // 密码修改失败
            throw new PasswordErrorException(MessageConstant.PASSWORD_EDIT_FAILED);
        }
    }

    /**
     * 更新用户邮箱
     * @param updateEmailDTO
     */
    @Override
    public void updateEmail(UpdateEmailDTO updateEmailDTO) {  // TODO 验证码首先要发给旧的还是新的？
        String newEmail = updateEmailDTO.getNewEmail();
        String code = updateEmailDTO.getCode();
        Long userId = BaseContext.getCurrentId();
        if (newEmail.isEmpty() || code.isEmpty()) {
            // 邮箱或验证码为空
            throw new UserException(MessageConstant.EMAIL_OR_CODE_IS_NULL);
        }
        // 校验验证码
        String key = "code:" + newEmail;
        if (!redisUtils.hasKey(key) || !redisUtils.get(key).equals(code)) {  // 不存在这个key或者输入code与redis中的不相同
            // 验证码错误
            throw new UserException(MessageConstant.EMAIL_CODE_MISTAKE);
        }
        // 校验通过，清除redis中的验证码数据
        redisUtils.delete(key);
        // 根据新email查询用户
        User emailUser = userMapper.selectOne(new QueryWrapper<User>().eq("email", newEmail));
        if (emailUser != null) {
            // 该邮箱已被注册
            throw new UserException(MessageConstant.EMAIL_HAS_BEEN_REGISTERED);
        }
        User user = userMapper.selectById(userId);
        user.setEmail(newEmail);
        user.setUpdateTime(new Date());
        //更新数据库
        userMapper.updateById(user);
    }

    /**
     * 修改密码
     * @param forgetPasswordDTO
     */
    @Override
    public void forgetPassword(ForgetPasswordDTO forgetPasswordDTO) {
        String newPassword = forgetPasswordDTO.getNewPassword();
        String code = forgetPasswordDTO.getCode();
        String email = forgetPasswordDTO.getEmail();
        // 查看redis中是否存在
        String key = "code:" + email;
        if (!redisUtils.hasKey(key) || !redisUtils.get(key).equals(code)) {  // 不存在这个key或者输入code与redis中的不相同
            // 验证码错误
            throw new UserException(MessageConstant.EMAIL_CODE_MISTAKE);
        }
        // 校验通过，清除redis中的验证码数据
        redisUtils.delete(key);
        // 修改密码
        String hashPW = BCrypt.hashpw(newPassword, BCrypt.gensalt());
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
        user.setPassword(hashPW);
        user.setUpdateTime(new Date());
        //更新数据库
        userMapper.updateById(user);
    }

    /**
     * 修改密钥
     * @param forgetSafeCodeDTO
     */
    @Override
    public void forgetSafeCode(ForgetSafeCodeDTO forgetSafeCodeDTO) {
        String email = forgetSafeCodeDTO.getEmail();
        String code = forgetSafeCodeDTO.getCode();
        String newSafeCode = forgetSafeCodeDTO.getNewSafeCode();
        // 查看redis中是否存在
        String key = "code:" + email;
        if (!redisUtils.hasKey(key) || !redisUtils.get(key).equals(code)) {  // 不存在这个key或者输入code与redis中的不相同
            // 验证码错误
            throw new UserException(MessageConstant.EMAIL_CODE_MISTAKE);
        }
        // 校验通过，清除redis中的验证码数据
        redisUtils.delete(key);
        // 修改密钥
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
        user.setSafeCode(newSafeCode);
        user.setUpdateTime(new Date());
        userMapper.updateById(user);
    }
}
